# Android 8 HAL 的变化
通过前面 传统 Hal 开发指南
的学习,我们知道,在 Android8 以前:
- Hal 是对驱动操作的包装库,操作硬件的具体逻辑可以放到 Hal 中,驱动程序本身只提供基本的功能。这样可以规避 Linux 内核的开源协议,硬件厂商提供 Hal 的 so 库即可。
- 形式上,Hal 是一个个的 so 库。
- Google 通过 hw_device_t 和 hw_module_t 等结构体给 Hal 层的实现提供了整体的结构和框架。
- Framework 中的程序通过 dlopen 函数来加载 so 库
Android8 之前的 Hal,本文称之为传统 Hal, 传统 Hal 的整体架构如下图所示:
到目前为止,一切都很美好,直到 Android 做大做强,Google 这边开发速度越来越快,新系统的发布越来越频繁。大量的老旧 Andorid 设备仍然跑着几年前的老系统。
为什么会这样呢?
在 Android8 以前,Framework 层由 Google 开发,一众下游厂商帮忙解 bug 加特性。HAL 层,芯片/方案厂商会根据自己主板的特性进行开发。每次 Google 那边 Framework 升级,Framework 与 Hal 之间的接口可能或多或少有变动,芯片/方案厂商就需要修改 Hal 层代码来适配 Framework 层的变化,适配好了,然后把源码下发到手机厂商,手机厂商拿到源码后又要接着来魔改 Framework 层。这就导致了,新系统升级需要最少三个阶段,费时费力,成本高,终端厂商升级意愿不强。
Google 的 boss 们肯定受不了,花了大价钱开发的软件,手机都升级不上去,我还怎么挣钱,这样不行,一群小弟拖老大的后腿。
为了挣更多的钱,在 Android8, Google 引入了一个叫 Treble 的架构,核心的修改主要有:
- 添加了 Vendor 分区,Hal 都打包到 Vendor 分区去,Framework 打包到 System 分区。两个分区中的程序不能链接对方的 so 库。
- HAL 不在是 so 库了,是一个个运行中的进程。
- 扩展了 Binder,添加了 Framework 分区中进程与 Vendor 分区中进程通信的 HwBinder,Vendor 分区内不同进程同级通信的 VndBinder。
- Framework 层通过 HwBinder RPC 调用访问 Vendor 中的 HAL 进程。
Android8 以后 Framework 与 HAL 之间的整体架构如下:
这个时候,只要我们保持 Framework 与 Vendor 之间的 Binder RPC 通信接口稳定,Framework 与 Vendor 就可以单独升级。Google 这边 Framework 开发好以后,终端手机厂商可以先用以前的 Vendor,直接用新的 Framework 来魔改,魔改好以后,直接推送 System 分区给老手机升级,芯片/方案厂商那边把 Vendor 升级好了以后,手机厂商拿到后,再把 Vendor 分区推送给老手机,或者 Vendor 就直接不升级了。这样,Google 开发的 Framework 就能更快的安装到用户手机上了。
理想很丰满,显示很骨感。
要实现上述场景的前提是,Vendor 分区中的 Natvie 程序/库只链接 Vendor 分区中的 so 库,Framework 分区中的 Native 程序/库只能链接 Framework 分区的库,不存在交叉链接的情况。另外还需要 Framework 和 Vendor 分区中的进程只通过 HwBinder RPC 通信,且通信的接口稳定。
实现上,这是不可能的,主要有两点原因:
- 部分 Vendor 的实现需要使用到 Framework 中的 so 库,这样 Vendor 分区中的程序就需要链接到 Framework 中的 so 库。
- 部分 HAL 在使用上有性能要求,使用 HwBinder RPC 访问性能不够看,这样 Framework 分区的程序就需要通过链接 Vendor 中的 Hal so 库来直接使用 HAL。
怎么解决呢?我们一个个看。
先看 Framework 分区中的 so 库怎么让 Vendor 分区链接到,而且不影响 System 分区的单独升级。
主要是两个手段:
手段一:Framework 中重要的 so 库,由 Google 来维护,Google 来保证这些库的 API 稳定性。这些库统称为 LL-NDK,包含了
libEGL.so, libGLESv1_CM.so, libGLESv2.so, libGLESv3.so, libandroid_net.so, libc.so, libdl.so, liblog.so, libm.so, libnativewindow.so, libneuralnetworks.so, libsync.so, libvndksupport.so, libvulkan.so
。这样,Framework 单独升级以后,由于这些库的 API 是稳定的,老的 Vendor 分区也是能正常工作的。手段二:如果一个 Framework 中的 so 库会被 Vendor 分区访问到,就把它复制一份到一个单独的路径中。这样, Framework 升级以后,Vendor 分区中用到的 Framework so 库是上一个版本的 Framework 中拷贝出来的,新的 Framework 中即使改变了这些 so 库的 API,也不影响 Vendor 的正常工作,这些拷贝两份的库称为 VNDK。另外需要知道的一点是,一个 so 库要成为 VDNK,需要满足一些要求:
- 与 Framework 中的程序没有 IPC 通信
- 不依赖于 ART 虚拟机
- 不读取/写入文件格式不稳定的文件/分区
- 没有需要法律审查的特殊软件许可
- 其代码所有者不反对供应商使用该库
我们接着看另一个问题,Vendor 分区中的 Hal so 库怎么让 Framework 分区链接到,而且不影响 System 分区的单独升级。
解决方案也大体雷同,Framework 要加载的 HAL so 库,也是统一由 google 维护,Google 来保持其 API 的稳定性。这些 HAL so 库统称为 Same-Process HAL (SP-HAL)。SP-HAL 库有以下一些:
- libGLESv1_CM_${driver}.so
- libGLESv2_${driver}.so
- libGLESv3_${driver}.so
- libEGL_${driver}.so
- vulkan.${driver}.so
- android.hardware.renderscript@1.0-impl.so
- android.hardware.graphics.mapper@2.0-impl.so
SP-HAL 也会链接 Framework 中的一些 so 库,这些 so 库必须是 VNDK 库或者 LL-NDK。一个 VNDK 库如果被 SP-HAL 链接了,我们就称之为 VNDK-SP 库。
官方的文档说,Google 的大佬们会加倍小心仔细检查 VNDK-SP 库,保证他们在 Framework 和 Vendor 中不出问题。
总结一下:
- 问题:Framework 和 Vendor 中存在交叉链接,影响 Framework 的单独升级
- 解法办法:
- Google 下场保障 so 库 API 稳定性
- 另外拷贝一份 so 库,Framework 和 Vendor 各用各的 so 库。
# HwBinder 指南
前面我提到,在 Android 8,Google 扩展了 Binder。本节主要分析其中的 HwBinder。
我们先回顾一下 Binder 的整体架构:
- Binder 是一个 linux 驱动程序,为应用层的跨进程函数调用(RPC)提供支持
- Binder 驱动对应的设备文件是
/dev/binder
- libbinder.so 库是对 Binder 驱动使用的包装库,方便应用程序的开发
- 在 RPC 过程中,被调用的一方称为服务,通常需要提前注册到 ServiceManager
- Binder 只能用于支持 Framework 中进程之间的 RPC 调用
HwBinder 与 Binder 的异同点如下:
- HwBinder 和 Binder 的驱动程序是同一个
- HwBinder 对应的设备文件是 /dev/hwbinder
- HwBinder 有自己的 native 应用层库 libhwbinder.so
- HwBinder 有自己的服务管家 HwServiceManager
- HwBinder 用于支持 Framework 进程访问 vendor 中的服务
HwBinder 的整体架构如下:
实际的开发以及后续的源码分析,我更多接触的是应用层库 libhwbinder.so,这个库基本就是 libbinder.so 库的翻版:
源文件对比:
(图片来自 https://blog.csdn.net/yangwen123/article/details/79836109?spm=1001.2014.3001.5502)
类对比:
(图片来自 https://blog.csdn.net/yangwen123/article/details/79836109?spm=1001.2014.3001.5502)
通信架构对比:
(图片来自 https://blog.csdn.net/yangwen123/article/details/79836109?spm=1001.2014.3001.5502)
这个库的实现了解一下就可以了,和 libbinder 大致类似,在 Android12 就不推荐使用了。
# HIDL HAL 简介
AIDL 用于生成代码框架,帮助我们快速实现 Binder 的服务端与客户端。
大多数情况下,HAL 层以进程加 HwBinder 服务的形式存在,Framework 层通过 HwBinder 调用到 HAL 层的 Binder 服务。HIDL 作用与 AIDL 类似,用于生成代码框架,帮助我们快速实现 HwBinder 的服务端与客户端。
根据 Google 文档的介绍,HIDL HAL 可以分为以下几种:
- Binderized HALs 绑定式:HAL 层以进程的形式存在,内部有一个 HwBinder 服务端对象,对外提供 HwBinder 远程调用服务。Framework 通过 HwBinder 远程调用到 HAL 中的函数,这些函数直接访问具体的驱动。
- Passthrough HALs 直通式:这种模式存在,主要是为了复用传统 HAL 的实现。HAL 层以进程的形式存在,内部有一个 HwBinder 服务端对象,对外提供 HwBinder 远程调用服务。Framework 通过 HwBinder 远程调用到 HAL 中的函数,这些函数会去加载传统 HAL 实现来操作具体硬件。
- Same-Process HALs 同进程式:有的 HAL 模块有性能需求,调用它们不能太慢了。这类 HAL 以 so 库的形式存在,Framework 层会直接链接这些 so 库,以保证调用的性能。
后续的章节,我们先写一个简单的 HIDL HAL 体验,接着在针对每一类 HAL,我们都会挑出一个源码中的实例来做详细分析。